#include "wl_http_client.h"
#include "wl_ext_http.h"
#include "wl_ext_fs.h"
#include "wl_net.h"
#include "repo.h"
#include "wlsdk_define.h"

#ifdef __cplusplus
extern "C" {
#endif

#define HTTP_CLIENT_MAX_PATH_LEN 20
#define HTTP_CLIENT_MAX_FILE_NUM 1
#define HTTP_CLIENT_TIMER_INTERVAL 1     // 1秒
#define HTTP_CLIENT_REFRESH_INTERVAL 30  // socket刷新间隔
#define HTTP_CLIENT_TIMEOUT 120          // 超时时间
#define HTTP_CLIENT_MAX_CONCURRENCY 1    //最多同时处理2个请求
#define HTTP_CLIENT_MAX_REQUEST 5        //最大请求数

typedef enum {
    HTTP_CLIENT_REQUEST_GET = 0,
    HTTP_CLIENT_REQUEST_GET_FILE,
    HTTP_CLIENT_REQUEST_POST
} wl_http_client_request_type;

typedef enum {
    HTTP_CLIENT_REQUEST_INIT = 0,  //初始化
    HTTP_CLIENT_REQUEST_PROCESS,   //处理中
    HTTP_CLIENT_REQUEST_FINISH     //结束
} wl_http_client_request_state;

typedef struct __wl_http_client_request {
    wl_ext_http_handle   request;
    wl_uint32            type;
    wl_uint32            state;
    wl_int32             timeout;  //请求超时时间 0 不限
    wl_int32             lifeleft;
    wl_http_client_callback callback;
    void*                context;
    wl_ext_fs_handle     fhandle;
    wl_char              file_path[HTTP_CLIENT_MAX_PATH_LEN];  //文件序号,以开机UTC作为开始
    wl_char*             content;
    wl_uint32            contentsize;
    wl_uint32            contentlen;
    wl_uint32            bodyLen;
    wl_char              body[1];
} wl_http_client_request;

typedef struct __protocol_client_global {
    wl_bool   initialized;
    wl_list   request_list;
    WL_TIMER  timer;
    wl_uint32 file_seq;
} wl_http_client_global;

static wl_http_client_global s_wl_http_client_global;

static wl_int32 execute_request(void);
static void wl_ext_wl_http_client_callback(wl_ext_http_handle handle, int status, void* pData, unsigned int dataLen);

static void request_error_callback(wl_http_client_request* request, wl_int32 result_code) {
    if (wl_null != request->callback) {
        request->callback(result_code, wl_null, 0, HTTP_CLIENT_CONTENT_NONE, request->context);
    }
}

static void release_request(wl_http_client_request* request) {
    if (request->request) {
        wl_ext_http_close(request->request);
        request->request = wl_null;
    }
    if (WL_EXT_INVALID_FS != request->fhandle) {
        wl_ext_fs_close(request->fhandle);
        request->fhandle = WL_EXT_INVALID_FS;
    }
    if (wl_null != request->content) {
        wl_free(request->content);
        request->content = wl_null;
    }
}

static void wl_http_client_timer_callback(WL_TIMER timer, void* context) {
    if (timer != s_wl_http_client_global.timer) {
        WLLOGW("timer %p is not equal local timer %p", timer, s_wl_http_client_global.timer);
        wl_timer_stop(timer);
        return;
    }
    {
        wl_list_iterator     iterator;
        wl_http_client_request* request =
            (wl_http_client_request*)wl_list_get_first(&s_wl_http_client_global.request_list, &iterator);
        while (!wl_list_iterator_is_end(&iterator)) {
            request->lifeleft -= HTTP_CLIENT_TIMER_INTERVAL;
            request->timeout -= HTTP_CLIENT_TIMER_INTERVAL;
            //请求超时
            if (0 >= request->lifeleft || 0 >= request->timeout) {
                request_error_callback(request, WLSDK_RESULT_LOCAL_REQUEST_TIMEOUT);
                wl_ext_http_close(request->request);
                release_request(request);
                wl_free(request);
                request = (wl_http_client_request*)wl_list_iterator_remove(&iterator);
            } else {
                request = (wl_http_client_request*)wl_list_get_next(&iterator);
            }
        }
    }
    execute_request();
}

static void wl_http_client_remove_request(wl_http_client_request *pRequest) {
    wl_list_iterator     iterator;
    wl_http_client_request* request =
        (wl_http_client_request*)wl_list_get_first(&s_wl_http_client_global.request_list, &iterator);

    while (!wl_list_iterator_is_end(&iterator)) {
        if (pRequest == request) {
            wl_list_iterator_remove(&iterator);
            release_request(pRequest);
            wl_free(pRequest);
            break;
        }else {
            request = (wl_http_client_request*)wl_list_get_next(&iterator);
        }
    }
}

static void wl_ext_wl_http_client_callback_internal(wl_ext_http_handle handle, int status, void* pData, unsigned int dataLen) {
    wl_http_client_request *request = (wl_http_client_request *)wl_ext_http_get_context(handle);
    int executeNext = 0;
    WLLOGW("wl_ext_wl_http_client_callback_internal status %d", status);
    if (status == WL_EXT_HTTP_BODY_DATA_STATUS) {
        if (request->type == HTTP_CLIENT_REQUEST_GET_FILE) {
            wl_ext_fs_write(request->fhandle, pData, dataLen);
        }else {
            WLLOGW("dataLen %d pData: %s", dataLen, pData);
            WLLOGW("request->contentlen %d request->contentsize %d", request->contentlen, request->contentsize);
            if (dataLen + request->contentlen > request->contentsize) {
                request->content = wl_realloc(request->content, dataLen + request->contentlen + 8 * 1024);
                if (request->content) {
                    memcpy(request->content + request->contentlen, pData, dataLen);
                    request->contentsize = dataLen + request->contentlen + 8 * 1024;
                    request->contentlen  = dataLen + request->contentlen;
                } else {
                    request->state = HTTP_CLIENT_REQUEST_FINISH;
                    WLLOGW("memory is not enough");
                    request_error_callback(request, WLSDK_RESULT_LOCAL_OUT_OF_MEMORY);
                    executeNext = 1;
                }
            } else {
                memcpy(request->content + request->contentlen, pData, dataLen);
                request->contentlen = dataLen + request->contentlen;
            }            
        }
    }else if (status == WL_EXT_HTTP_FINISH_STATUS) {
        WLLOGW("WL_EXT_HTTP_FINISH_STATUS");
        request->state = HTTP_CLIENT_REQUEST_FINISH;
        if (request->type == HTTP_CLIENT_REQUEST_GET_FILE) {        
            wl_ext_fs_close(request->fhandle);
            request->fhandle = WL_EXT_INVALID_FS;

            request->callback(WLSDK_RESULT_SUCCESS,
                    request->file_path,
                    strlen(request->file_path),
                    HTTP_CLIENT_CONTENT_FILE_PATH,
                    request->context);
        }else if (request->type == HTTP_CLIENT_REQUEST_POST) {
            WLLOGW("WL_EXT_HTTP_FINISH_STATUS request->contentlen %d content: %s", request->contentlen, request->content);
            request->callback(WLSDK_RESULT_SUCCESS,
                    request->content,
                    request->contentlen,
                    HTTP_CLIENT_CONTENT_JSON,
                    request->context);
        }else {
            WLLOGW("WL_EXT_HTTP_FINISH_STATUS request->contentlen %d content: %s", request->contentlen, request->content);
            request->callback(WLSDK_RESULT_SUCCESS,
                    request->content,
                    request->contentlen,
                    HTTP_CLIENT_CONTENT_BINARY,
                    request->context);            
        }
        executeNext = 1;
    }else if (status == WL_EXT_HTTP_ERROR_STATUS) {
        int errorCode = *(int *)pData;
        request->state = HTTP_CLIENT_REQUEST_FINISH;
        request_error_callback(request, errorCode);
        executeNext = 1;
    }else if (status == WL_EXT_HTTP_HEADER_STATUS) {
        WLLOGW("print headers: %s\n", pData);
    }

    if (executeNext) {
        wl_http_client_remove_request(request);
        execute_request();
    }
}

static void wl_ext_wl_http_client_callback(wl_ext_http_handle handle, int status, void* pData, unsigned int dataLen) {
    wl_task_message message;
    wl_ext_http_message *net_message = wl_malloc(sizeof(wl_ext_http_message) + dataLen);

    if (net_message == NULL) {
        WLLOGW("memory is not enough");
        return;
    }

    memset(&message, 0, sizeof(wl_task_message));
    memset(net_message, 0, sizeof(wl_ext_http_message) + dataLen);

    WLLOGW("status %d dataLen %d", status, dataLen);

    net_message->handle = handle;
    net_message->status = status;
    if (pData) {
        memcpy(net_message->data, pData, dataLen);
        net_message->dataLen = dataLen;
    }
    net_message->callback = wl_ext_wl_http_client_callback_internal;

    message.type = WL_TASK_MSG_HTTP;
    memcpy(message.data, &net_message, sizeof(wl_ext_http_message *));
    message.datalen = sizeof(wl_ext_http_message *);

    wl_send_task_message(WL_TASK_APP, &message);
}


static wl_int32 execute_request(void) {
    int ret = -1;
    wl_list_iterator     iterator;
    wl_uint32            concurrency = 0;
    wl_http_client_request* request =
        (wl_http_client_request*)wl_list_get_first(&s_wl_http_client_global.request_list, &iterator);
    while (!wl_list_iterator_is_end(&iterator)) {
        //并发限制
        if (concurrency++ >= HTTP_CLIENT_MAX_CONCURRENCY) {
            break;
        }

        WLLOGW("request->state %d concurrency %d", request->state, concurrency);

        //待处理(新HTTP请求插入尾部,遍历头部判断请求是否处理即可)
        if (HTTP_CLIENT_REQUEST_INIT == request->state) {
            if (request->type == HTTP_CLIENT_REQUEST_GET_FILE || request->type == HTTP_CLIENT_REQUEST_GET) {
                if (request->type == HTTP_CLIENT_REQUEST_GET_FILE) {
                    ret = wl_ext_http_get_file(request->request, request->file_path, wl_ext_wl_http_client_callback, request);
                }else {
                    ret = wl_ext_http_get_data(request->request, wl_ext_wl_http_client_callback, request);
                }
            }else {
                wl_ext_http_set_header(request->request, "Content-Type", "application/json");
                ret = wl_ext_http_post_data(request->request, request->body, request->bodyLen, wl_ext_wl_http_client_callback, request);
            }
            if (0 > ret) {
                wl_ext_http_close(request->request);
                request_error_callback(request, WLSDK_RESULT_LOCAL_HOST_DNS_ERROR);
                request = (wl_http_client_request*)wl_list_iterator_remove(&iterator);
                release_request(request);
                wl_free(request);
                continue;
            } else {
                request->state = HTTP_CLIENT_REQUEST_PROCESS;
            }
        }
        request = (wl_http_client_request*)wl_list_get_next(&iterator);
    }
    //没有请求,停止定时器
    if (0 >= wl_list_size(&s_wl_http_client_global.request_list)) {
        if (WL_INVALID_TIMER != s_wl_http_client_global.timer) {
            wl_timer_stop(s_wl_http_client_global.timer);
            s_wl_http_client_global.timer = WL_INVALID_TIMER;
        }
    }
    return 0;
}

static wl_int32 add_request(wl_char*             url,
                            wl_uint32            type,
                            wl_char*             content,
                            wl_uint32            content_len,
                            wl_uint32            content_type,
                            wl_uint32            timeout,
                            wl_http_client_callback callback,
                            void*                context) {
    wl_list_iterator     iterator;
    wl_http_client_request* request =
        (wl_http_client_request*)wl_list_get_first(&s_wl_http_client_global.request_list, &iterator);
    while (!wl_list_iterator_is_end(&iterator)) {
        //文件下载只允许1路并发
        if (HTTP_CLIENT_REQUEST_GET_FILE == request->type) {
            WLLOGW("http request get file has processing");
            return -1;
        }
        request = (wl_http_client_request*)wl_list_get_next(&iterator);
    }
    WLLOGD("url %s", url);
    //请求超过限制
    if (HTTP_CLIENT_MAX_REQUEST <= wl_list_size(&s_wl_http_client_global.request_list)) {
        WLLOGW("http request too much");
        return -1;
    }
    //空队列
    if (0 >= wl_list_size(&s_wl_http_client_global.request_list)) {
        if (WL_INVALID_TIMER != s_wl_http_client_global.timer) {
            wl_timer_stop(s_wl_http_client_global.timer);
            s_wl_http_client_global.timer = WL_INVALID_TIMER;
        }
        //超时定时器
        s_wl_http_client_global.timer = wl_timer_start(HTTP_CLIENT_TIMER_INTERVAL * 1000,
                                                    REPETITIVE,
                                                    wl_http_client_timer_callback,
                                                    wl_null,
                                                    WL_TASK_APP,
                                                    "wl_http_client");
        if (WL_INVALID_TIMER == s_wl_http_client_global.timer) {
            WLLOGW("wl_timer_start fail");
            return -1;
        }
    }
    //添加新请求
    {
        wl_http_client_request* client_request = wl_null;
        client_request = (wl_http_client_request*)wl_malloc(sizeof(wl_http_client_request) + content_len);
        if (wl_null == client_request) {
            return -1;
        }

        memset(client_request, 0, sizeof(wl_http_client_request) + content_len + 1);
        if (type == HTTP_CLIENT_REQUEST_GET_FILE) {
            client_request->request = wl_ext_http_request_create(url, WL_EXT_HTTP_GET_FILE);
        }else if (type == HTTP_CLIENT_REQUEST_GET) {
            client_request->request = wl_ext_http_request_create(url, WL_EXT_HTTP_GET_DATA);
        }else if (type == HTTP_CLIENT_REQUEST_POST) {
            memcpy(client_request->body, content, content_len);
            client_request->bodyLen = content_len;
            client_request->request = wl_ext_http_request_create(url, WL_EXT_HTTP_POST);
        }
        client_request->timeout     = timeout ? timeout : 120;
        client_request->type        = type;
        client_request->state       = HTTP_CLIENT_REQUEST_INIT;
        client_request->lifeleft    = HTTP_CLIENT_REFRESH_INTERVAL;
        client_request->callback    = callback;
        client_request->context     = context;
        client_request->fhandle     = WL_EXT_INVALID_FS;
        client_request->content     = wl_malloc(1024);
        client_request->contentsize = 1024;
        client_request->contentlen  = 0;
        if (wl_null == client_request->content) {
            wl_ext_http_close(request->request);
            release_request(client_request);
            wl_free(client_request);
            WLLOGW("malloc content fail");
            return -1;
        }
        //获取文件
        if (HTTP_CLIENT_REQUEST_GET_FILE == type) {
            if (wl_null != content) {
                strncpy(client_request->file_path, content, sizeof(client_request->file_path) - 1);
                client_request->file_path[sizeof(client_request->file_path) - 1] = '\0';
            } else {
                sprintf(client_request->file_path, "file_%d", s_wl_http_client_global.file_seq % HTTP_CLIENT_MAX_FILE_NUM);
                s_wl_http_client_global.file_seq++;
            }

            client_request->fhandle = wl_ext_fs_open(client_request->file_path, WL_EXT_CREAT | WL_EXT_WRONLY);
            if (WL_EXT_INVALID_FS == client_request->fhandle) {
                wl_ext_http_close(request->request);
                release_request(client_request);
                wl_free(client_request);
                WLLOGW("wl_fopen error");
                return -1;
            }
        }
        wl_list_add(&s_wl_http_client_global.request_list, client_request, -1);
    }
    execute_request();
    return 0;
}

wl_int32 wl_http_client_init(void) {
    if (!s_wl_http_client_global.initialized) {

        if (0 != wl_ext_http_init()) {
            WLLOGW("wl_ext_http_init fail");
            return -1;
        }

        if (0 != wl_list_init(&s_wl_http_client_global.request_list)) {
            WLLOGW("wl_list_init fail");
            return -1;
        }
        s_wl_http_client_global.file_seq    = 0;
        s_wl_http_client_global.initialized = wl_true;
    }
    return 0;
}

wl_int32 wl_http_client_end(void) {
    if (s_wl_http_client_global.initialized) {
        wl_list_iterator     iterator;
        wl_http_client_request* request =
            (wl_http_client_request*)wl_list_get_first(&s_wl_http_client_global.request_list, &iterator);
        while (!wl_list_iterator_is_end(&iterator)) {
            request_error_callback(request, WLSDK_RESULT_LOCAL_SYSTEM_RELEASE);
            release_request(request);
            request = (wl_http_client_request*)wl_list_get_next(&iterator);
        }
        wl_list_free(&s_wl_http_client_global.request_list);
        if (WL_INVALID_TIMER != s_wl_http_client_global.timer) {
            wl_timer_stop(s_wl_http_client_global.timer);
            s_wl_http_client_global.timer = WL_INVALID_TIMER;
        }
        s_wl_http_client_global.initialized = wl_false;

        wl_ext_http_deinit();
    }
    WLLOGI("wl_http_client_end");
    return 0;
}

wl_int32 wl_http_client_get(wl_char* url, wl_uint32 timeout, wl_http_client_callback callback, void* context) {
    return add_request(url, HTTP_CLIENT_REQUEST_GET, wl_null, 0, HTTP_CLIENT_CONTENT_NONE, timeout, callback, context);
}

wl_int32 wl_http_client_get_file(wl_char* url, wl_uint32 timeout, wl_http_client_callback callback, void* context) {
    return add_request(
        url, HTTP_CLIENT_REQUEST_GET_FILE, wl_null, 0, HTTP_CLIENT_CONTENT_NONE, timeout, callback, context);
}

wl_int32 wl_http_client_get_file_with_path(wl_char*             url,
                                        wl_char*             path,
                                        wl_uint32            timeout,
                                        wl_http_client_callback callback,
                                        void*                context) {
    return add_request(url,
                       HTTP_CLIENT_REQUEST_GET_FILE,
                       path,
                       strlen(path) + 1,
                       HTTP_CLIENT_CONTENT_NONE,
                       timeout,
                       callback,
                       context);
}

wl_int32 wl_http_client_post(wl_char*             url,
                          wl_uint32            timeout,
                          wl_char*             content,
                          wl_uint32            content_len,
                          wl_uint32            content_type,
                          wl_http_client_callback callback,
                          void*                context) {
    return add_request(url, HTTP_CLIENT_REQUEST_POST, content, content_len, content_type, timeout, callback, context);
}

#ifdef __cplusplus
}
#endif
